<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    .dropDown{width: 200px;height: 40px;line-height: 40px;margin: 20px auto;}
    .dropDown span{display: block;border: solid 1px black;}
    .dropDown .list{margin: 0;padding: 0;list-style: none;border: solid 1px black;border-top:none;text-indent: 4px;display: none;}
    .dropDown .list li.hover{background: red;color:#fff;}
    .dropDown .list li[data-selected="selected"]::after{content:"√";float: right;margin-right: 10px;}
  </style>
</head>
<body>

  <select>
    <option value="">北京</option>
    <option value="">上海</option>
    <option value="">深圳</option>
    <option value="">广州</option>
    <option value="">杭州</option>
    <option value="">南京</option>
  </select>

  <div class="dropDown">
    <span>北京</span>
    <ul class="list">
      <li data-selected="selected">北京</li>
      <li>上海</li>
      <li>深圳</li>
      <li>广州</li>
      <li>杭州</li>
      <li>南京</li>
    </ul>
  </div>

</body>
<script>

  var span = document.querySelector(".dropDown span");
  var list = document.querySelector(".dropDown .list");
  var li = document.querySelectorAll(".dropDown .list li");

  // - 键盘和鼠标的联动

  // 12. 默认选中项的索引
  var index = 2;
  for(var i=0;i<li.length;i++){
    delete li[i].dataset.selected;
  }
  li[index].dataset.selected = "selected";
  span.innerHTML = li[index].innerHTML;


  // 1. 记录下拉菜单的显示状态：true要显示，false要隐藏
  var onoff = true;

  // 2. 绑定显示区域的点击事件
  span.addEventListener("click", function( eve ){
    var e = eve || window.event;
    // 3. 阻止事件冒泡
    e.stopPropagation();
    // 4. 根据状态，决定显示或隐藏，同步状态
    if(onoff){
      list.style.display = "block";
      onoff = false;
      // 32. 每次打开下拉菜单，都需要初始化
      if(keyIndex !== undefined){
        li[keyIndex].classList.remove("hover")
        keyIndex = undefined;
      }
    }else{
      list.style.display = "none";
      onoff = true;
    }
  })

  // 5. 点击文档的任何区域，隐藏下拉菜单，同步状态
  document.onclick = function(){
    list.style.display = "none";
    onoff = true;
  }

  // 6. 给每个li绑定鼠标相关事件
  for(var i=0;i<li.length;i++){
    // 14. 给每个li绑定索引
    li[i].index = i;
    // 7. 鼠标进入，添加hover
    li[i].onmouseover = function(){
      // 33. 鼠标进入时，清除键盘索引的高亮样式
      if(keyIndex !== undefined){
        li[keyIndex].classList.remove("hover");
      }
      this.classList.add("hover")
      // 34. 将鼠标的索引设置给键盘的索引
      keyIndex = this.index;
    }
    // 8. 鼠标进入，删除hover
    li[i].onmouseout = function(){
      this.classList.remove("hover")
    }
    // 9. 鼠标点击，设置选中
    li[i].onclick = function(){
      // 13. 清除之前的选中标志
      li[index].dataset.selected = "";
      // 10. 设置内容
      span.innerHTML = this.innerHTML;
      // 11. 添加选中标志
      this.dataset.selected = "selected";
      // 15. 将本次点击的索引，设置成下次要清除索引
      index = this.index;
    }
  }

  // 35. 鼠标离开下拉菜单
  list.onmouseout = function(){
    // 36. 如果键盘索引不是初始值，清除对应高亮
    if(keyIndex !== undefined){
      li[keyIndex].classList.remove("hover");
      // 同时将键盘索引初始化
      keyIndex = undefined;
    }
  }

  // ======

  // 16. 创建键盘控制的索引
  var keyIndex;

  // 17. 添加键盘事件
  document.onkeydown = function(eve){
    // 37. 如果下拉菜单，未显示，不应该有键盘功能
    if( onoff ) return;

    var e = eve || window.event;
    var code = e.keyCode || e.which;
    // 18. 上键的功能
    if(code === 38){
      // 19. 第一次按下上键
      if(keyIndex === undefined){
        // 20. 索引为最后一个项的索引
        keyIndex = li.length-1;
      }else if(keyIndex === 0){
        // 21. 如果索引到终点了，索引一直为终点值
        keyIndex = 0;
      }else{
        // 22. 在改变索引之前，清除之前索引对应的样式
        li[keyIndex].classList.remove("hover");
        // 23. 既不是第一次按下，也不是到终点，索引--
        keyIndex--;
      }
      // 24. 改变索引之后，给当前索引项添加样式
      li[keyIndex].classList.add("hover");
    }
    // 25. 下键的功能
    if(code === 40){
      if(keyIndex === undefined){
        keyIndex = 0
      }else if(keyIndex === li.length-1){
        keyIndex = li.length-1;
      }else{
        li[keyIndex].classList.remove("hover");
        keyIndex++;
      }
      li[keyIndex].classList.add("hover");
    }
    // 26. 回车的功能
    if(code === 13){
      // 27. 将当前键盘索引对应的内容设置到显示区域
      span.innerHTML = li[keyIndex].innerHTML
      // 28. 清除之前索引对应项的选中标志
      li[index].dataset.selected = "";
      // 29. 给当前索引对应的项添加选中标志
      li[keyIndex].dataset.selected = "selected";
      // 30. 将本次索引设置成下次要清除的索引
      index = keyIndex;
      // 31. 隐藏下拉菜单，并同步状态
      list.style.display = "none";
      onoff = true;
    }
  }

  
  
</script>
</html>